/**
   Realiza peticiones automaticas sobre el servidor de operaciones aritmeticas.

   La cadencia de envio de mensajes se establece mediante un temporizador.
   Los operadores se calculan de forma aleatoria.

   El servidor de este cliente: server.p
*/

#include <iostream>

#include <string.h>
#include <unistd.h>

#include <nemesis.h>
#include <nemesis.app.functions.h>
#include <nemesis.comm.h>

#include <nemesis.timex.TimeController.h>
#include <nemesis.timex.Clock.h>

#include <test.Response.h>
#include <test.Request.h>

class Sender : public timex::Clock {
public:
   Sender () : Clock ("Sender", 1000), 
      a_messageBySecond (0), 
      a_nquarter (0), 
      a_requests ("Request"), 
      a_errorCounter (0),
      a_txMessageCounter (0)
   {;}

   void setMessageBySecond (const int messageBySecond) throw () { a_messageBySecond = messageBySecond; }

   int getTxMessageCounter () const throw () { return a_txMessageCounter; }

private:
   int a_messageBySecond;
   int a_nquarter;
   int a_errorCounter;
   int a_txMessageCounter;
   ThreadData <test::Request> a_requests;

   void tick () throw (RuntimeException);
};

class MyCommunicator : public Communicator {
public:
   MyCommunicator () : Communicator (), a_avgResponseTime (0), a_rxMessageCounter (0), a_responses ("Response")  {;}

private:
   ThreadData <test::Response> a_responses;
   int a_avgResponseTime;
   int a_rxMessageCounter;

   void eventReceiveMessage (ClientSocket &, const Message&)
      throw (RuntimeException);

   void eventBreakConnection (const ClientSocket&) throw ();

   void eventBreakConnection (Server* server) throw () {
      comm::Communicator::eventBreakConnection (server);
   }
   void eventBreakConnection (const Service* service) throw () { 
      comm::Communicator::eventBreakConnection (service);
   }
   
   static bool isOk (const test::Response& response) throw ();
};

class HeavyClient : public nemesis::comm::Application {
public:
   HeavyClient ();

   Server* getServer () const throw () { return a_server; }
   const Sender* getSender () const throw () { return &a_sender; }

private:
   MyCommunicator a_communicator;
   timex::TimeController a_timeController;
   Sender a_sender;
   Server* a_server;

   void initialize () throw (RuntimeException);
   void run () throw (RuntimeException);
};

using namespace std;

int main (int argc, const char** argv)
{
   CommandLine& commandLine (CommandLine::instanciate ());
   HeavyClient app;

   srand (time (NULL));

   try {
      commandLine.initialize (argv, argc);
      commandLine.verify ();

      Logger::setLevel (Logger::Information);
      string traceFile ("client.");
      traceFile += nemesis::functions::asString ((int) getpid ());
      traceFile += ".trace";
      Logger::initialize ("arithmeticClient", new TraceWriter (traceFile.c_str (),4096000));

      app.start ();
   }
   catch (Exception& ex) {
      cout << ex.asString () << endl;
   }

   return 0;
}

HeavyClient::HeavyClient () :
   Application ("arithmeticClient", "Cliente de operaciones aritm�icas con transport SDP", "1.0"),
   a_communicator (),
   a_timeController (1000, 250)
{
   CommandLine& commandLine (CommandLine::instanciate ());

   commandLine.add ("p", CommandLine::Argument::Mandatory, "Puerto en el que el servidor atiende respuestas.");
   commandLine.add ("a", CommandLine::Argument::Mandatory, "Direccin IP Puerto en el que el servidor atiende respuestas.");
   commandLine.add ("n", CommandLine::Argument::Mandatory, "Numero de mensajes por segundo");
   commandLine.add ("trace", CommandLine::Argument::Optional, "Nivel de trazas (debug,warning, error,...)");
}

void HeavyClient::initialize ()
   throw (RuntimeException)
{
   CommandLine& cl (CommandLine::instanciate ());

   Network& network = Network::instanciate ();

   a_server = network.createServer (cl.getValue ("a"), cl.getIntegerValue ("p"), true);
   a_sender.setMessageBySecond (cl.getIntegerValue ("n"));

   if (cl.exists ("trace"))
      Logger::setLevel (Logger::asLevel (cl.getValue ("trace")));
}

void HeavyClient::run ()
   throw (RuntimeException)
{
   a_timeController.activate (a_sender);

   a_communicator.accept ();
}

void MyCommunicator::eventReceiveMessage (ClientSocket&, const Message& message)
   throw (RuntimeException)
{
   test::Response& response = a_responses.get ();
   response.decode (message.getBody ());

   const nemesis::Milisecond now = nemesis::functions::milisecond ();
   const int delay =  now - (Milisecond) response.initTime;

   if (delay > 0 && isOk (response) == true) {
      a_rxMessageCounter ++;
      a_avgResponseTime += delay;

      LOGINFORMATION (
         string msg = nemesis::functions::asString (
            "%d %c %d = %d", response.x, response.op, response.y, response.result
         );
         msg += nemesis::functions::asText (" | Delay: ", delay);
         Logger::information (msg, FILE_LOCATION);
      );
   }
   else {
      LOGWARNING (
         string msg = nemesis::functions::asString (
            "Flip: %d %c %d = %d", response.x, response.op, response.y, response.result
         );
         msg += nemesis::functions::asText (" | Message: ", message.getBody ());
         msg += nemesis::functions::asText (" | Delay: ", delay);
         Logger::warning (msg, FILE_LOCATION);
      );
   }
}

void MyCommunicator::eventBreakConnection (const ClientSocket& clientSocket)
   throw ()
{
   if (a_rxMessageCounter == 0)
      return;

   LOGNOTICE (
      HeavyClient& app = static_cast <HeavyClient&> (nemesis::app::functions::getApp ());   
      string msg ("Tiempo medio respuesta: ");
      msg += nemesis::functions::asString (a_avgResponseTime / a_rxMessageCounter);
      msg += " ms";
      msg += nemesis::functions::asText (" | Rx: ", a_rxMessageCounter);
      msg += nemesis::functions::asText (" | Tx: ", app.getSender ()->getTxMessageCounter ());
      Logger::notice (msg, FILE_LOCATION);

      cout << msg << endl << endl;
   );
   requestStop ();
   comm::Communicator::eventBreakConnection (clientSocket);
}

bool MyCommunicator::isOk (const test::Response& response) 
   throw ()
{
   if (response.op != '+' && response.op != '-' && response.op != '*' && response.op != '/') 
      return false;
      
   int result = 0;
   
   switch (response.op) {
      case '+':
         result = response.x + response.y;
         break;
      case '-':
         result = response.x - response.y;
         break;
      case '*':
         result = response.x * response.y;
         break;
      case '/':
         result = (response.y != 0) ? (response.x / response.y): 0;
         break;
   }
    
   return result == response.result;   
}

void Sender::tick ()
   throw (RuntimeException)
{
   Server* server = static_cast <HeavyClient&> (nemesis::app::functions::getApp ()).getServer ();
   Communicator* communicator = nemesis::app::functions::component <Communicator> (FILE_LOCATION);

   if (a_errorCounter > 100) {
      communicator->requestStop ();
      Logger::warning ("Terminado por errores continuos en la conexion", FILE_LOCATION);
      return;
   }

   test::Request& request = a_requests.get ();

   for (int n = 0; n < a_messageBySecond && communicator->hasRequestedStop () == false; n ++) {
      request.op = '+';
      request.x = rand () % 1000;
      request.y = rand () % 1000;
      request.initTime = nemesis::functions::milisecond ();

      try {
         server->send (request);
         a_txMessageCounter ++;
      }
      catch (RuntimeException& ex) {
         a_errorCounter ++;
         ex.trace ();
         break;
      }
   }
}

